// Copyright (C) 2015, Alberto Corona <alberto@0x1a.us>
// All rights reserved. This file is part of core-utils, distributed under the
// GPL v3 license. For full terms please see the LICENSE file.

#![crate_type = "bin"]
#![crate_name = "ln"]
#![feature(path_ext,collections)]

static VERS: &'static str = "0.1.0";
static PROG: &'static str = "ln";

extern crate getopts;
extern crate util;

use getopts::{Options};
use util::{Status};

use std::env;
use std::fs;
use std::os;
use std::path::{PathBuf};
use std::fs::{PathExt};

fn unix_symlink(from: &PathBuf, to: &PathBuf, verbose: bool) {
    match os::unix::fs::symlink(from, to) {
        Ok(_) => {
            if verbose {
                println!("{} -> {}", from.display(), to.display());
            }
        },
        Err(e) => {
            util::err(PROG, Status::Error, e.to_string());
        }
    };
}

fn sym_link_files(files: Vec<String>, to: &PathBuf, force: bool, verbose: bool) {
    for item in files.iter() {
        if !to.is_dir() {
            util::err(PROG, Status::Error, String::from(String::from(
                        to.to_str().unwrap()) + " is not a directory"));
        } else {
            soft_link(&PathBuf::from(item), &to.join(PathBuf::from(item)), force, verbose);
        }
    }
}

fn hard_link_files(files: Vec<String>, to: &PathBuf, force: bool, verbose: bool) {
    for item in files.iter() {
        if !to.is_dir() {
            util::err(PROG, Status::Error, String::from(String::from(
                        to.to_str().unwrap()) + " is not a directory"));
        } else {
            let to_path = PathBuf::from(item);
            hard_link(&to_path, &to.join(&to_path), force, verbose);
        }
    }
}

fn soft_link(from: &PathBuf, to: &PathBuf, force: bool, verbose: bool) {
    if !force {
        if cfg!(target_family = "unix") {
            unix_symlink(from, to, verbose);
        }
    } else {
        if PathBuf::from(to).exists() {
            fs::remove_file(to).ok();
            if cfg!(target_family = "unix") {
                unix_symlink(from, to, verbose);
            }
        }
    }
}

fn hard_link(from: &PathBuf, to: &PathBuf, force: bool, verbose: bool) {
    if !force {
        match fs::hard_link(from, to) {
            Ok(_) => {
                if verbose {
                    println!("{} -> {}", from.display(), to.display());
                }
            },
            Err(e) => {
                util::err(PROG, Status::Error, e.to_string());
            }
        };
    } else {
        if PathBuf::from(to).exists() {
            fs::remove_file(to).ok();
            match fs::hard_link(from, to) {
                Ok(_) => { },
                Err(e) => {
                    util::err(PROG, Status::Error, e.to_string());
                }
            };
        }
    }
}

fn print_usage(prog: &str, opts: Options) {
    let breif = format!("Usage: {} [OPTION] TARGET LINK_NAME\nCreate links between files", prog);
    print!("{}", opts.usage(&breif));
    util::exit(Status::Ok);
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let mut opts = Options::new();

    opts.optflag("h", "help", "Print the help menu");
    opts.optflag("", "version", "Print the version");
    opts.optflag("v", "verbose", "Verbose print the operation");
    opts.optflag("f", "force", "Force operation. Removes existsing target files");
    opts.optflag("s", "soft", "Create a symbolic link");
    opts.optflag("p", "physical", "Create a hard link");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => { m },
        Err(f) => {
            util::err(PROG, Status::OptError, f.to_string());
            panic!(f.to_string());
        }
    };

    if matches.opt_present("h") {
        print_usage(PROG, opts);
    } else if matches.opt_present("version") {
        util::copyright(PROG, VERS, "2015", vec!["Alberto Corona"]);
    } else {
        let verb = matches.opt_present("v");
        let force = matches.opt_present("f");
        let src = match matches.free.first() {
            Some(s) => { PathBuf::from(s) },
            None => {
                util::err(PROG, Status::Error, String::from(" no arguments given"));
                panic!();
            },
        };
        let dest = PathBuf::from(matches.free.last().unwrap());
        if matches.free.len() == 1 {
            println!("{}: no link name for {}", PROG, src.display());
            util::exit(Status::Error);
        }
        if matches.opt_present("s") {
            if matches.free.len() > 2 {
                sym_link_files(matches.free.init().to_vec(), &dest, force, verb);
            } else {
                soft_link(&src, &PathBuf::from(&matches.free[1]), force, verb);
            }
        } else if matches.opt_present("p") {
            if matches.free.len() > 2 {
                hard_link_files(matches.free.init().to_vec(), &dest, force, verb);
            } else {
                hard_link(&src, &PathBuf::from(&matches.free[1]), force, verb);
            }
        }
    }
}
